package org.softmed.rest.generation;

import java.io.File;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.softmed.filehandling.ClassFinder;
import org.softmed.filehandling.selectors.GroovyFileSelector;
import org.softmed.filehandling.selectors.JavaFileSelector;
import org.softmed.persistence.PersistenceManager;
import org.softmed.persistence.PersistenceManagerProvider;
import org.softmed.reflection.utils.FieldInfo;
import org.softmed.reflection.utils.ReflectionUtil;
import org.softmed.rest.config.Application;
import org.softmed.rest.config.Module;
import org.softmed.rest.config.Resource;
import org.softmed.rest.generation.builders.BaseCreator;
import org.softmed.rest.generation.fields.FieldNameList;
import org.softmed.rest.generation.fields.GenerationConfig;
import org.softmed.rest.generation.refactoring.ClassService;
import org.softmed.rest.generation.refactoring.ModuleService;
import org.softmed.rest.generation.scafold.AppConfig;
import org.softmed.rest.generation.scafold.ModConfig;
import org.softmed.rest.generation.suites.HandlerSuiteManager;
import org.softmed.rest.generation.uri.ClassURIBuilder;
import org.softmed.rest.generation.uri.URIBuilder;

import com.google.inject.Inject;
import com.google.inject.name.Named;

public class DefaultRESTModuleBuilder implements RESTModuleBuilder {
	// URIManager uriManager = new URIManager();

	ReflectionUtil reflectUtil = new ReflectionUtil();

	BaseCreator baseCreator = new BaseCreator();

	@Inject
	@Named("listCreator")
	RESTCreator listCreator; // = new ListRESTCreator();

	@Inject
	@Named("primitiveCreator")
	RESTCreator primitiveCreator; // = new
	// PrimitiveFieldsAndWrappersRESTCreator();

	@Inject
	@Named("restrictedPrimitiveCreator")
	RESTCreator restrictedPrimitiveCreator; // = new
	// PrimitiveOnlyGetAllowedCreator();

	@Inject
	@Named("stringCreator")
	RESTCreator stringCreator; // = new StringFieldCreator();

	@Inject
	@Named("nonNativeCreator")
	RESTCreator nonNativeCreator; // = new NonNativeFieldCreator();

	// private Application app;

	public ModuleService buildModule(AppConfig app, ModConfig mod,
			Application application, Module module, String directoryPath,
			GenerationConfig generationConfig, String providerName,
			String xmlSuite, String handlerSuiteName) throws Throwable {

		String xmlSuiteToUse = xmlSuite;
		if (xmlSuite != null)
			xmlSuiteToUse = xmlSuite;

		HandlerSuite handlerSuite = null;
		if (handlerSuiteName == null)
			handlerSuite = HandlerSuiteManager.getDefaultSuite();
		else
			handlerSuite = HandlerSuiteManager.getSuite(handlerSuiteName);

		List<Class> classes = mod.getClasses();

		ModuleService moduleService = new ModuleService();
		moduleService.setDirectoryName(new File(mod.getPath()).getName());

		for (Class class1 : classes) {
			ClassService classService = new ClassService();
			processClass(app, mod, application, class1, module, classes,
					generationConfig, providerName, xmlSuiteToUse, handlerSuite);
			moduleService.getClasses().add(classService);

		}

		return moduleService;
	}

	private ClassService processClass(AppConfig app1, ModConfig mod,
			Application app, Class type, Module module, List<Class> classes,
			GenerationConfig generationConfig, String providerName,
			String xmlSuite, HandlerSuite handlerSuite) throws Throwable {

		ClassService classService = new ClassService();

		String parameter = reflectUtil.getHifenizedName(type.getSimpleName())
				+ "-id";
		// String baseURI = "";
		URIBuilder uriBuilder = app1.getUriBuilder();
		String baseURI = uriBuilder.getRelativeURI(type);

		processBaseURI(type, baseURI, parameter, module, providerName,
				xmlSuite, handlerSuite);

		FieldNameList omited = generationConfig.getOmitFields(type);
		FieldCreationParameters parameters = new FieldCreationParameters();
		parameters.setGenerationConfig(generationConfig);
		parameters.setModule(module);
		parameters.setParameter(parameter);
		parameters.setProviderName(providerName);
		parameters.setType(type);
		parameters.setXmlSuite(xmlSuite);
		parameters.setHandlerSuite(handlerSuite);

		FieldInfo pkinfo = null;

		PersistenceManagerProvider manager = new PersistenceManagerProvider(
				providerName);

		PersistenceManager util = manager.getPersistenceManager();

		util.connect();
		pkinfo = util.getPrimaryKeyInfo(type);
		Collection<FieldInfo> genFields = util.getGeneratedFields(type);
		util.close();

		List<FieldInfo> fields = reflectUtil.getFieldInfos(type);
		for (FieldInfo fieldInfo : fields) {
			try {
				String fieldName = fieldInfo.getName();
				if (omited != null
						&& omited.getFieldNames().contains(fieldName))
					continue;

				String alias = generationConfig.getFieldAlias(fieldInfo
						.getDeclaringClass(), fieldName);
				if (alias != null)
					fieldName = alias;

				parameters.setFieldInfo(fieldInfo);
				parameters.setRestFieldName(reflectUtil
						.getHifenizedName(fieldName));
				parameters.setUri(baseURI);

				if (pkinfo != null
						&& pkinfo.getName().equals(fieldInfo.getName())) {
					restrictedPrimitiveCreator.createRESTConfig(parameters);
					continue;
				}

				if (reflectUtil.isPrimitiveOrProxy(fieldInfo.getType())
						|| fieldInfo.getType() == Date.class) {
					boolean limitToGetOnly = false;
					limitToGetOnly = isFieldGenerated(fieldInfo, genFields);

					if (limitToGetOnly)
						restrictedPrimitiveCreator.createRESTConfig(parameters);
					else
						primitiveCreator.createRESTConfig(parameters);

					continue;
				}

				if (fieldInfo.getType().equals(String.class)) {
					stringCreator.createRESTConfig(parameters);
					continue;
				}

				if (List.class.isAssignableFrom(fieldInfo.getType())) {
					listCreator.createRESTConfig(parameters);
				} else {
					nonNativeCreator.createRESTConfig(parameters);
				}

			} catch (Throwable t) {
				t.printStackTrace();
				System.err.println("Error creating REST service for field "
						+ fieldInfo.getName() + " of type "
						+ fieldInfo.getType().getCanonicalName()
						+ " from Class " + type.getCanonicalName());
			}
		}

		// Class parentClass = type.getSuperclass();
		// if (parentClass != null && parentClass != Object.class)
		// processClass(app1, mod, app, type, parentClass, module, classes,
		// generationConfig, providerName, xmlSuite, handlerSuite);

		return classService;

	}

	private boolean isFieldGenerated(FieldInfo fieldInfo,
			Collection<FieldInfo> genFields) {
		if (genFields == null)
			return false;
		for (FieldInfo field : genFields) {
			if (field.getName().equals(fieldInfo.getName()))
				return true;
		}
		return false;
	}

	private void processBaseURI(Class type, String uri, String parameter,
			Module module, String providerName, String xmlSuite,
			HandlerSuite handlerSuite) throws Throwable {

		Resource resource = baseCreator.createBasicPostXML(type, module, uri,
				providerName, xmlSuite, handlerSuite);
		baseCreator.getPagedList(resource, type, providerName, xmlSuite,
				handlerSuite);
		resource = baseCreator.createGetResourceAsXML(module, uri + "/{"
				+ parameter + "}", parameter, type, providerName, xmlSuite,
				handlerSuite);
		// TODO delete - check to remove from lists...
		baseCreator.createDeleteResource(resource,
				uri + "/{" + parameter + "}", parameter, type, providerName,
				xmlSuite, handlerSuite);
		// TODO PUT - very complicated
		baseCreator.createPutResource(resource, uri + "/{" + parameter + "}",
				parameter, type, providerName, xmlSuite, handlerSuite);
	}

	public BaseCreator getBaseCreator() {
		return baseCreator;
	}

	public void setBaseCreator(BaseCreator baseCreator) {
		this.baseCreator = baseCreator;
	}

	public RESTCreator getListCreator() {
		return listCreator;
	}

	public void setListCreator(RESTCreator listCreator) {
		this.listCreator = listCreator;
	}

	public RESTCreator getPrimitiveCreator() {
		return primitiveCreator;
	}

	public void setPrimitiveCreator(RESTCreator primitiveCreator) {
		this.primitiveCreator = primitiveCreator;
	}

	public RESTCreator getRestrictedPrimitiveCreator() {
		return restrictedPrimitiveCreator;
	}

	public void setRestrictedPrimitiveCreator(
			RESTCreator restrictedPrimitiveCreator) {
		this.restrictedPrimitiveCreator = restrictedPrimitiveCreator;
	}

	public RESTCreator getStringCreator() {
		return stringCreator;
	}

	public void setStringCreator(RESTCreator stringCreator) {
		this.stringCreator = stringCreator;
	}

	public RESTCreator getNonNativeCreator() {
		return nonNativeCreator;
	}

	public void setNonNativeCreator(RESTCreator nonNativeCreator) {
		this.nonNativeCreator = nonNativeCreator;
	}

}
